home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 97 / CD-ROM 97 / CD-ROM 97.iso / internet / ghostzilla / ghsetup.exe / chrome / comm.jar / content / editor / EdDialogCommon.js < prev    next >
Encoding:
JavaScript  |  2002-04-09  |  33.8 KB  |  1,258 lines

  1. /*
  2.  * The contents of this file are subject to the Netscape Public
  3.  * License Version 1.1 (the "License"); you may not use this file
  4.  * except in compliance with the License. You may obtain a copy of
  5.  * the License at http://www.mozilla.org/NPL/
  6.  *
  7.  * Software distributed under the License is distributed on an "AS
  8.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  9.  * implied. See the License for the specific language governing
  10.  * rights and limitations under the License.
  11.  *
  12.  * The Original Code is Mozilla Communicator client code, released
  13.  * March 31, 1998.
  14.  *
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation. Portions created by Netscape are
  17.  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Pete Collins
  22.  *   Brian King
  23.  */
  24.  
  25. // Each editor window must include this file
  26. // Variables  shared by all dialogs:
  27. var editorShell;
  28.  
  29. // Object to attach commonly-used widgets (all dialogs should use this)
  30. var gDialog = {};
  31.  
  32. var gValidationError = false;
  33.  
  34. // Use for 'defaultIndex' param in InitPixelOrPercentMenulist
  35. var gPixel = 0;
  36. var gPercent = 1;
  37.  
  38. var maxPixels  = 10000;
  39. // For dialogs that expand in size. Default is smaller size see "onMoreFewer()" below
  40. var SeeMore = false;
  41.  
  42. // A XUL element with id="location" for managing
  43. // dialog location relative to parent window
  44. var gLocation;
  45.  
  46. // The element being edited - so AdvancedEdit can have access to it
  47. var globalElement;
  48.  
  49. function InitEditorShell()
  50. {
  51.     // get the editor shell from the parent window
  52.  
  53.   editorShell = window.opener.editorShell;
  54.   if (editorShell) {
  55.     editorShell = editorShell.QueryInterface(Components.interfaces.nsIEditorShell);
  56.   }
  57.   if (!editorShell) {
  58.     dump("EditorShell not found!!!\n");
  59.     window.close();
  60.     return false;
  61.   }
  62.  
  63.   // Save as a property of the window so it can be used by child dialogs
  64.  
  65.   window.editorShell = editorShell;
  66.  
  67.   return true;
  68. }
  69.  
  70. /* Validate contents of an input field 
  71.  *
  72.  *  inputWidget    The 'textbox' XUL element for text input of the attribute's value
  73.  *  listWidget     The 'menulist' XUL element for choosing "pixel" or "percent"
  74.  *                  May be null when no pixel/percent is used.
  75.  *  minVal         minimum allowed for input widget's value
  76.  *  maxVal         maximum allowed for input widget's value
  77.  *                 (when "listWidget" is used, maxVal is used for "pixel" maximum,
  78.  *                  100% is assumed if "percent" is the user's choice)
  79.  *  element        The DOM element that we set the attribute on. May be null.
  80.  *  attName        Name of the attribute to set.  May be null or ignored if "element" is null
  81.  *  mustHaveValue  If true, error dialog is displayed if "value" is empty string
  82.  *
  83.  *  This calls "ValidateNumberRange()", which puts up an error dialog to inform the user. 
  84.  *    If error, we also: 
  85.  *      Shift focus and select contents of the inputWidget,
  86.  *      Switch to appropriate panel of tabbed dialog if user implements "SwitchToValidate()",
  87.  *      and/or will expand the dialog to full size if "More / Fewer" feature is implemented
  88.  *
  89.  *  Returns the "value" as a string, or "" if error or input contents are empty
  90.  *  The global "gValidationError" variable is set true if error was found
  91.  */
  92. function ValidateNumber(inputWidget, listWidget, minVal, maxVal, element, attName, mustHaveValue, mustShowMoreSection)
  93. {
  94.   if (!inputWidget)
  95.   {
  96.     gValidationError = true;
  97.     return "";
  98.   }
  99.  
  100.   // Global error return value
  101.   gValidationError = false;
  102.   var maxLimit = maxVal;
  103.   var isPercent = false;
  104.  
  105.   var numString = TrimString(inputWidget.value);
  106.   if (numString || mustHaveValue)
  107.   {
  108.     if (listWidget)
  109.       isPercent = (listWidget.selectedIndex == 1);
  110.     if (isPercent)
  111.       maxLimit = 100;
  112.  
  113.     // This method puts up the error message
  114.     numString = ValidateNumberRange(numString, minVal, maxLimit, mustHaveValue);
  115.     if(!numString)
  116.     {
  117.       // Switch to appropriate panel for error reporting
  118.       SwitchToValidatePanel();
  119.  
  120.       // or expand dialog for users of "More / Fewer" button
  121.       if ("dialog" in window && dialog && 
  122.            "MoreSection" in gDialog && gDialog.MoreSection)
  123.       {
  124.         if ( !SeeMore )
  125.           onMoreFewer();
  126.       }
  127.  
  128.       // Error - shift to offending input widget
  129.       SetTextboxFocus(inputWidget);
  130.       gValidationError = true;
  131.     }
  132.     else
  133.     {
  134.       if (isPercent)
  135.         numString += "%";
  136.       if (element)
  137.         element.setAttribute(attName, numString);
  138.     }
  139.   } else if (element) {
  140.     element.removeAttribute(attName);
  141.   }
  142.   return numString;
  143. }
  144.  
  145. /* Validate contents of an input field 
  146.  *
  147.  *  value          number to validate
  148.  *  minVal         minimum allowed for input widget's value
  149.  *  maxVal         maximum allowed for input widget's value
  150.  *                 (when "listWidget" is used, maxVal is used for "pixel" maximum,
  151.  *                  100% is assumed if "percent" is the user's choice)
  152.  *  mustHaveValue  If true, error dialog is displayed if "value" is empty string
  153.  *
  154.  *  If inputWidget's value is outside of range, or is empty when "mustHaveValue" = true,
  155.  *      an error dialog is popuped up to inform the user. The focus is shifted
  156.  *      to the inputWidget.
  157.  *
  158.  *  Returns the "value" as a string, or "" if error or input contents are empty
  159.  *  The global "gValidationError" variable is set true if error was found
  160.  */
  161. function ValidateNumberRange(value, minValue, maxValue, mustHaveValue)
  162. {
  163.   // Initialize global error flag
  164.   gValidationError = false;
  165.   value = TrimString(String(value));
  166.  
  167.   // We don't show error for empty string unless caller wants to
  168.   if (!value && !mustHaveValue)
  169.     return "";
  170.  
  171.   var numberStr = "";
  172.  
  173.   if (value.length > 0)
  174.   {
  175.     // Extract just numeric characters
  176.     var number = Number(value.replace(/\D+/g, ""));
  177.     if (number >= minValue && number <= maxValue )
  178.     {
  179.       // Return string version of the number
  180.       return String(number);
  181.     }
  182.     numberStr = String(number);
  183.   }
  184.  
  185.   var message = "";
  186.  
  187.   if (numberStr.length > 0)
  188.   {
  189.     // We have a number from user outside of allowed range
  190.     message = GetString( "ValidateRangeMsg");
  191.     message = message.replace(/%n%/, numberStr);
  192.     message += "\n ";
  193.   }
  194.   message += GetString( "ValidateNumberMsg");
  195.  
  196.   // Replace variable placeholders in message with number values
  197.   message = message.replace(/%min%/, minValue).replace(/%max%/, maxValue);
  198.   ShowInputErrorMessage(message);
  199.  
  200.   // Return an empty string to indicate error
  201.   gValidationError = true;
  202.   return "";
  203. }
  204.  
  205. function SetTextboxFocusById(id)
  206. {
  207.   SetTextboxFocus(document.getElementById(id));
  208. }
  209.  
  210. function SetTextboxFocus(textbox)
  211. {
  212.   if (textbox)
  213.   {
  214.     // Until .select works for editable menulist, lets just set focus
  215.     //XXX Using the setTimeout is hacky workaround for bug 103197
  216.     // Must create a new function to keep "textbox" in scope
  217.     setTimeout( function(textbox) { textbox.focus(); }, 0, textbox );
  218. /*
  219.     // Select entire contents
  220.     if (textbox.value.length > 0)
  221.       // This doesn't work for editable menulists yet
  222.       textbox.select();
  223.     else
  224.       textbox.focus();
  225. */
  226.   }
  227. }
  228.  
  229. function ShowInputErrorMessage(message)
  230. {
  231.   AlertWithTitle(GetString("InputError"), message);
  232.   window.focus();
  233. }
  234.  
  235. // Get the text appropriate to parent container
  236. //  to determine what a "%" value is refering to.
  237. // elementForAtt is element we are actually setting attributes on
  238. //  (a temporary copy of element in the doc to allow canceling),
  239. //  but elementInDoc is needed to find parent context in document
  240. function GetAppropriatePercentString(elementForAtt, elementInDoc)
  241. {
  242.   var name = elementForAtt.nodeName.toLowerCase();
  243.   if ( name == "td" || name == "th")
  244.     return GetString("PercentOfTable");
  245.  
  246.   // Check if element is within a table cell
  247.   if (editorShell.GetElementOrParentByTagName("td", elementInDoc))
  248.     return GetString("PercentOfCell");
  249.   else
  250.     return GetString("PercentOfWindow");
  251. }
  252.  
  253. function AppendStringToMenulistById(menulist, stringID)
  254. {
  255.   return AppendStringToMenulist(menulist, GetString(stringID));
  256. }
  257.  
  258. function AppendStringToMenulist(menulist, string)
  259. {
  260.   if (menulist)
  261.   {
  262.     var menupopup = menulist.firstChild;
  263.     // May not have any popup yet -- so create one
  264.     if (!menupopup)
  265.     {
  266.       menupopup = document.createElementNS(XUL_NS, "menupopup");
  267.       if (menupopup)
  268.         menulist.appendChild(menupopup);
  269.       else
  270.       {
  271.         dump("Failed to create menupoup\n");
  272.         return null;
  273.       }
  274.     }
  275.     var menuItem = document.createElementNS(XUL_NS, "menuitem");
  276.     if (menuItem)
  277.     {
  278.       menuItem.setAttribute("label", string);
  279.       menupopup.appendChild(menuItem);
  280.       return menuItem;
  281.     }
  282.   }
  283.   return null;
  284. }
  285.  
  286. function AppendLabelAndValueToMenulist(menulist, labelStr, valueStr)
  287. {
  288.   if (menulist)
  289.   {
  290.     var menupopup = menulist.firstChild;
  291.     // May not have any popup yet -- so create one
  292.     if (!menupopup)
  293.     {
  294.       menupopup = document.createElementNS(XUL_NS, "menupopup");
  295.       if (menupopup)
  296.         menulist.appendChild(menupopup);
  297.       else
  298.       {
  299.         dump("Failed to create menupoup\n");
  300.         return null;
  301.       }
  302.     }
  303.     var menuItem = document.createElementNS(XUL_NS, "menuitem");
  304.     if (menuItem)
  305.     {
  306.       menuItem.setAttribute("label", labelStr);
  307.       menuItem.setAttribute("value", valueStr);
  308.       menupopup.appendChild(menuItem);
  309.       return menuItem;
  310.     }
  311.   }
  312.   return null;
  313. }
  314.  
  315. function ClearMenulist(menulist)
  316. {
  317.   // Always use "AppendStringToMenulist" so we know there's 
  318.   //  just one <menupopup> as 1st child of <menulist>
  319.   if (menulist) {
  320.     menulist.selectedItem = null;
  321.     var popup = menulist.firstChild;
  322.     if (popup)
  323.       while (popup.firstChild)
  324.         popup.removeChild(popup.firstChild);
  325.   }
  326. }
  327.  
  328. function ClearListbox(listbox)
  329. {
  330.   if (listbox)
  331.   {
  332.     listbox.clearSelection();
  333.     while (listbox.firstChild)
  334.       listbox.removeChild(listbox.firstChild);
  335.   }
  336. }
  337.  
  338. /* These help using a <tree> for simple lists
  339.   Assumes this simple structure:
  340.   <tree>
  341.     <treecols><treecol flex="1"/></treecols>
  342.     <treechildren>
  343.       <treeitem>
  344.         <treerow>
  345.           <treecell label="the text the user sees"/>
  346. */
  347.  
  348. function AppendStringToTreelistById(tree, stringID)
  349. {
  350.   return AppendStringToTreelist(tree, GetString(stringID));
  351. }
  352.  
  353. function AppendStringToTreelist(tree, string)
  354. {
  355.   if (tree)
  356.   {
  357.     var treecols = tree.firstChild;
  358.     if (!treecols)
  359.     {
  360.       dump("Bad XUL: Must have <treecols> as first child\n");
  361.     }
  362.     var treechildren = treecols.nextSibling;
  363.     if (!treechildren)
  364.     {
  365.       treechildren = document.createElementNS(XUL_NS, "treechildren");
  366.       if (treechildren)
  367.       {
  368.         treechildren.setAttribute("flex","1");
  369.         tree.appendChild(treechildren);
  370.       }
  371.       else
  372.       {
  373.         dump("Failed to create <treechildren>\n");
  374.         return null;
  375.       }
  376.     }
  377.     var treeitem = document.createElementNS(XUL_NS, "treeitem");
  378.     var treerow = document.createElementNS(XUL_NS, "treerow");
  379.     var treecell = document.createElementNS(XUL_NS, "treecell");
  380.     if (treeitem && treerow && treecell)
  381.     {
  382.       treerow.appendChild(treecell);
  383.       treeitem.appendChild(treerow);
  384.       treechildren.appendChild(treeitem)
  385.       treecell.setAttribute("label", string);
  386.       //var len = Number(tree.getAttribute("length"));
  387.       //if (!len) len = -1;
  388.       tree.setAttribute("length", treechildren.childNodes.length);
  389.       return treeitem;
  390.     }
  391.   }
  392.   return null;
  393. }
  394.  
  395. function ClearTreelist(tree)
  396. {
  397.   if (tree)
  398.   {
  399.     tree.treeBoxObject.selection.clearSelection();
  400.     // Skip over the first <treecols> child
  401.     if (tree.firstChild)
  402.     {
  403.       var nextChild = tree.firstChild.nextSibling;
  404.       while (nextChild)
  405.       {
  406.         var nextTmp = nextChild.nextSibling;
  407.         tree.removeChild(nextChild);
  408.         nextChild = nextTmp;
  409.       }
  410.     }
  411.     // Count list items
  412.     tree.setAttribute("length", 0);
  413.   }
  414. }
  415.  
  416. function GetSelectedTreelistAttribute(tree, attr)
  417. {
  418.   if (tree)
  419.   {
  420.     if (tree.treeBoxObject.selection.count > 0) {
  421.       var selectedItem = tree.contentView.getItemAtIndex(tree.currentIndex);
  422.       if (selectedItem &&
  423.           selectedItem.firstChild &&
  424.           selectedItem.firstChild.firstChild)
  425.       {
  426.         return selectedItem.firstChild.firstChild.getAttribute(attr);
  427.       }
  428.     }
  429.   }
  430.   return "";
  431. }
  432.  
  433. function GetSelectedTreelistValue(tree)
  434. {
  435.   return GetSelectedTreelistAttribute(tree,"label")
  436. }
  437.  
  438. function RemoveSelectedTreelistItem(tree)
  439. {
  440.   if (tree)
  441.   {
  442.     if (tree.treeBoxObject.selection.count > 0)
  443.     {
  444.       // Get the node to delete
  445.       var treeItem = tree.contentView.getItemAtIndex(tree.currentIndex);
  446.       if (treeItem)
  447.       {
  448.         tree.treeBoxObject.selection.clearSelection();
  449.         var parent = treeItem.parentNode;
  450.         if (parent)
  451.         {
  452.           parent.removeChild(treeItem);
  453.           tree.setAttribute("length", parent.childNodes.length);
  454.         }
  455.       }
  456.     }
  457.   }
  458. }
  459.  
  460. function GetTreelistValueAt(tree, index)
  461. {
  462.   if (tree)
  463.   {
  464.     var item = tree.getItemAtIndex(index);
  465.     if (item && item.firstChild && item.firstChild.firstChild)
  466.       return item.firstChild.firstChild.getAttribute("label");
  467.   }
  468.   return "";
  469. }
  470.  
  471. function forceInteger(elementID)
  472. {
  473.   var editField = document.getElementById( elementID );
  474.   if ( !editField )
  475.     return;
  476.  
  477.   var stringIn = editField.value;
  478.   if (stringIn && stringIn.length > 0)
  479.   {
  480.     // Strip out all nonnumeric characters
  481.     stringIn = stringIn.replace(/\D+/g,"");
  482.     if (!stringIn) stringIn = "";
  483.  
  484.     // Write back only if changed
  485.     if (stringIn != editField.value)
  486.       editField.value = stringIn;
  487.   }
  488. }
  489.  
  490. function LimitStringLength(elementID, length)
  491. {
  492.   var editField = document.getElementById( elementID );
  493.   if ( !editField )
  494.     return;
  495.  
  496.   var stringIn = editField.value;
  497.   if (stringIn && stringIn.length > length)
  498.     editField.value = stringIn.slice(0,length);
  499. }
  500.  
  501. function StripPxUnit(value)
  502. {
  503.   var pxIndex = value.search(/px/);
  504.   if (pxIndex  > 0) {
  505.     // Strip out the unit
  506.     value = value.substr(0, pxIndex);
  507.   }
  508.   return value;
  509. }
  510.  
  511. function InitPixelOrPercentMenulist(elementForAtt, elementInDoc, attribute, menulistID, defaultIndex)
  512. {
  513.   if (!defaultIndex) defaultIndex = gPixel;
  514.  
  515.   // var size  = elementForAtt.getAttribute(attribute);
  516.   var size = GetHTMLOrCSSStyleValue(elementForAtt, attribute, attribute)
  517.   var menulist = document.getElementById(menulistID);
  518.   var pixelItem;
  519.   var percentItem;
  520.  
  521.   if (!menulist)
  522.   {
  523.     dump("NO MENULIST found for ID="+menulistID+"\n");
  524.     return size;
  525.   }
  526.  
  527.   ClearMenulist(menulist);
  528.   pixelItem = AppendStringToMenulist(menulist, GetString("Pixels"));
  529.  
  530.   if (!pixelItem) return 0;
  531.  
  532.   percentItem = AppendStringToMenulist(menulist, GetAppropriatePercentString(elementForAtt, elementInDoc));
  533.   if (size && size.length > 0)
  534.   {
  535.     // Search for a "%" or "px"
  536.     var percentIndex = size.search(/%/);
  537.     var pxIndex = size.search(/px/);
  538.     if (percentIndex > 0)
  539.     {
  540.       // Strip out the %
  541.       size = size.substr(0, percentIndex);
  542.       if (percentItem)
  543.         menulist.selectedItem = percentItem;
  544.     }
  545.     else if (pxIndex  > 0)
  546.     {
  547.       // Strip out the %
  548.       size = size.substr(0, pxIndex);
  549.       menulist.selectedItem = pixelItem;
  550.     }
  551.     else
  552.       menulist.selectedItem = pixelItem;
  553.   }
  554.   else
  555.     menulist.selectedIndex = defaultIndex;
  556.  
  557.   return size;
  558. }
  559.  
  560. function onAdvancedEdit()
  561. {
  562.   // First validate data from widgets in the "simpler" property dialog
  563.   if (ValidateData())
  564.   {
  565.     // Set true if OK is clicked in the Advanced Edit dialog
  566.     window.AdvancedEditOK = false;
  567.     // Open the AdvancedEdit dialog, passing in the element to be edited
  568.     //  (the copy named "globalElement")
  569.     window.openDialog("chrome://editor/content/EdAdvancedEdit.xul", "_blank", "chrome,close,titlebar,modal,resizable=yes", "", globalElement);
  570.     window.focus();
  571.     if (window.AdvancedEditOK)
  572.     {
  573.       // Copy edited attributes to the dialog widgets:
  574.       InitDialog();
  575.     }
  576.   }
  577. }
  578.  
  579. function getColor(ColorPickerID)
  580. {
  581.   var colorPicker = document.getElementById(ColorPickerID);
  582.   var color;
  583.   if (colorPicker)
  584.   {
  585.     // Extract color from colorPicker and assign to colorWell.
  586.     color = colorPicker.getAttribute("color");
  587.     if (color && color == "")
  588.       return null;
  589.     // Clear color so next if it's called again before
  590.     //  color picker is actually used, we dedect the "don't set color" state
  591.     colorPicker.setAttribute("color","");
  592.   }
  593.  
  594.   return color;
  595. }
  596.  
  597. function setColorWell(ColorWellID, color)
  598. {
  599.   var colorWell = document.getElementById(ColorWellID);
  600.   if (colorWell)
  601.   {
  602.     if (!color || color == "")
  603.     {
  604.       // Don't set color (use default)
  605.       // Trigger change to not show color swatch
  606.       colorWell.setAttribute("default","true");
  607.       // Style in CSS sets "background-color",
  608.       //   but color won't clear unless we do this:
  609.       colorWell.removeAttribute("style");
  610.     }
  611.     else
  612.     {
  613.       colorWell.removeAttribute("default");
  614.       // Use setAttribute so colorwell can be a XUL element, such as button
  615.       colorWell.setAttribute("style", "background-color:"+color);
  616.     }
  617.   }
  618. }
  619.  
  620. function getColorAndSetColorWell(ColorPickerID, ColorWellID)
  621. {
  622.   var color = getColor(ColorPickerID);
  623.   setColorWell(ColorWellID, color);
  624.   return color;
  625. }
  626.  
  627. function InitMoreFewer()
  628. {
  629.   // Set SeeMore bool to the OPPOSITE of the current state,
  630.   //   which is automatically saved by using the 'persist="more"'
  631.   //   attribute on the gDialog.MoreFewerButton button
  632.   //   onMoreFewer will toggle it and redraw the dialog
  633.   SeeMore = (gDialog.MoreFewerButton.getAttribute("more") != "1");
  634.   onMoreFewer();
  635. }
  636.  
  637. function onMoreFewer()
  638. {
  639.   if (SeeMore)
  640.   {
  641.     gDialog.MoreSection.setAttribute("collapsed","true");
  642.     gDialog.MoreFewerButton.setAttribute("more","0");
  643.     gDialog.MoreFewerButton.setAttribute("label",GetString("MoreProperties"));
  644.     SeeMore = false;
  645.   }
  646.   else
  647.   {
  648.     gDialog.MoreSection.removeAttribute("collapsed");
  649.     gDialog.MoreFewerButton.setAttribute("more","1");
  650.     gDialog.MoreFewerButton.setAttribute("label",GetString("FewerProperties"));
  651.     SeeMore = true;
  652.   }
  653.   window.sizeToContent();
  654. }
  655.  
  656. function SwitchToValidatePanel()
  657. {
  658.   // no default implementation
  659.   // Only EdTableProps.js currently implements this
  660. }
  661.  
  662. const nsIFilePicker = Components.interfaces.nsIFilePicker;
  663.  
  664. function GetLocalFileURL(filterType)
  665. {
  666.   var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  667.   var fileType = "html";
  668.  
  669.   if (filterType == "img")
  670.   {
  671.     fp.init(window, GetString("SelectImageFile"), nsIFilePicker.modeOpen);
  672.     fp.appendFilters(nsIFilePicker.filterImages);
  673.     fileType = "image";
  674.   }
  675.   // Current usage of this is in Link dialog,
  676.   //  where we always want HTML first
  677.   else if (filterType.indexOf("html") == 0)
  678.   {
  679.     fp.init(window, GetString("OpenHTMLFile"), nsIFilePicker.modeOpen);
  680.  
  681.     // When loading into Composer, direct user to prefer HTML files and text files,
  682.     //   so we call separately to control the order of the filter list
  683.     fp.appendFilters(nsIFilePicker.filterHTML);
  684.     fp.appendFilters(nsIFilePicker.filterText);
  685.  
  686.     // Link dialog also allows linking to images
  687.     if (filterType.indexOf("img") > 0)
  688.       fp.appendFilters(nsIFilePicker.filterImages);
  689.  
  690.   }
  691.   // Default or last filter is "All Files"
  692.   fp.appendFilters(nsIFilePicker.filterAll);
  693.  
  694.   // set the file picker's current directory to last-opened location saved in prefs
  695.   SetFilePickerDirectory(fp, fileType);
  696.  
  697.  
  698.   /* doesn't handle *.shtml files */
  699.   try {
  700.     var ret = fp.show();
  701.     if (ret == nsIFilePicker.returnCancel)
  702.       return null;
  703.   }
  704.   catch (ex) {
  705.     dump("filePicker.chooseInputFile threw an exception\n");
  706.     return null;
  707.   }
  708.   SaveFilePickerDirectory(fp, fileType);
  709.   
  710.   var ioService = GetIOService();
  711.   return fp.file ? ioService.getURLSpecFromFile(fp.file) : null;
  712. }
  713.  
  714. function GetMetaElement(name)
  715. {
  716.   if (name)
  717.   {
  718.     name = name.toLowerCase();
  719.     if (name != "")
  720.     {
  721.       var metaNodes = editorShell.editorDocument.getElementsByTagName("meta");
  722.       if (metaNodes && metaNodes.length > 0)
  723.       {
  724.         for (var i = 0; i < metaNodes.length; i++)
  725.         {
  726.           var metaNode = metaNodes.item(i);
  727.           if (metaNode && metaNode.getAttribute("name") == name)
  728.             return metaNode;
  729.         }
  730.       }
  731.     }
  732.   }
  733.   return null;
  734. }
  735.  
  736. function CreateMetaElement(name)
  737. {
  738.   var metaElement = editorShell.CreateElementWithDefaults("meta");
  739.   if (metaElement)
  740.     metaElement.setAttribute("name", name);
  741.   else
  742.     dump("Failed to create metaElement!\n");
  743.  
  744.   return metaElement;
  745. }
  746.  
  747. function GetHTTPEquivMetaElement(name)
  748. {
  749.   if (name)
  750.   {
  751.     name = name.toLowerCase();
  752.     if (name != "")
  753.     {
  754.       var metaNodes = editorShell.editorDocument.getElementsByTagName("meta");
  755.       if (metaNodes && metaNodes.length > 0)
  756.       {
  757.         for (var i = 0; i < metaNodes.length; i++)
  758.         {
  759.           var metaNode = metaNodes.item(i);
  760.           if (metaNode)
  761.           {
  762.             var httpEquiv = metaNode.getAttribute("http-equiv");
  763.             if (httpEquiv && httpEquiv.toLowerCase() == name)
  764.               return metaNode;
  765.           }
  766.         }
  767.       }
  768.     }
  769.   }
  770.   return null;
  771. }
  772.  
  773. function CreateHTTPEquivMetaElement(name)
  774. {
  775.   metaElement = editorShell.CreateElementWithDefaults("meta");
  776.   if (metaElement)
  777.     metaElement.setAttribute("http-equiv", name);
  778.   else
  779.     dump("Failed to create httpequivMetaElement!\n");
  780.  
  781.   return metaElement;
  782. }
  783.  
  784. function CreateHTTPEquivElement(name)
  785. {
  786.   metaElement = editorShell.CreateElementWithDefaults("meta");
  787.   if (metaElement)
  788.     metaElement.setAttribute("http-equiv", name);
  789.   else
  790.     dump("Failed to create metaElement for http-equiv!\n");
  791.  
  792.   return metaElement;
  793. }
  794.  
  795. // Change "content" attribute on a META element,
  796. //   or delete entire element it if content is empty
  797. // This uses undoable editor transactions
  798. function SetMetaElementContent(metaElement, content, insertNew)
  799. {
  800.   if (metaElement)
  801.   {
  802.     if(!content || content == "")
  803.     {
  804.       if (!insertNew)
  805.         editorShell.DeleteElement(metaElement);
  806.     }
  807.     else
  808.     {
  809.       if (insertNew)
  810.       {
  811.         metaElement.setAttribute("content", content);
  812.         AppendHeadElement(metaElement);
  813.       }
  814.       else
  815.         editorShell.SetAttribute(metaElement, "content", content);
  816.     }
  817.   }
  818. }
  819.  
  820. function GetHeadElement()
  821. {
  822.   var headList = editorShell.editorDocument.getElementsByTagName("head");
  823.   if (headList)
  824.     return headList.item(0);
  825.  
  826.   return null;
  827. }
  828.  
  829. function AppendHeadElement(element)
  830. {
  831.   var head = GetHeadElement();
  832.   if (head)
  833.   {
  834.     var position = 0;
  835.     if (head.hasChildNodes())
  836.       position = head.childNodes.length;
  837.  
  838.     // Use editor's undoable transaction
  839.     // Last param "true" says "don't change the selection"
  840.     editorShell.InsertElement(element, head, position, true);
  841.   }
  842. }
  843.  
  844. function SetWindowLocation()
  845. {
  846.   gLocation = document.getElementById("location");
  847.   if (gLocation)
  848.   {
  849.     window.screenX = Math.max(0, Math.min(window.opener.screenX + Number(gLocation.getAttribute("offsetX")),
  850.                                           screen.availWidth - window.outerWidth));
  851.     window.screenY = Math.max(0, Math.min(window.opener.screenY + Number(gLocation.getAttribute("offsetY")),
  852.                                           screen.availHeight - window.outerHeight));
  853.   }
  854. }
  855.  
  856. function SaveWindowLocation()
  857. {
  858.   if (gLocation)
  859.   {
  860.     var newOffsetX = window.screenX - window.opener.screenX;
  861.     var newOffsetY = window.screenY - window.opener.screenY;
  862.     gLocation.setAttribute("offsetX", window.screenX - window.opener.screenX);
  863.     gLocation.setAttribute("offsetY", window.screenY - window.opener.screenY);
  864.   }
  865. }
  866.  
  867. function onCancel()
  868. {
  869.   SaveWindowLocation();
  870.   // Close dialog by returning true
  871.   return true;
  872. }
  873.  
  874. function SetRelativeCheckbox(checkbox)
  875. {
  876.   if (!checkbox) {
  877.     checkbox = document.getElementById("MakeRelativeCheckbox");
  878.     if (!checkbox)
  879.       return;
  880.   }
  881.  
  882.   // Mail never allows relative URLs, so hide the checkbox
  883.   if (editorShell.editorType == "htmlmail")
  884.   {
  885.     checkbox.setAttribute("collapsed", "true");
  886.     return;
  887.   }
  888.  
  889.   var input =  document.getElementById(checkbox.getAttribute("for"));
  890.   if (!input)
  891.     return;
  892.  
  893.   var url = TrimString(input.value);
  894.   var urlScheme = GetScheme(url);
  895.  
  896.   // Check it if url is relative (no scheme).
  897.   checkbox.checked = url.length > 0 && !urlScheme;
  898.  
  899.   // Now do checkbox enabling:
  900.   var enable = false;
  901.  
  902.   var docUrl = GetDocumentBaseUrl();
  903.   var docScheme = GetScheme(docUrl);
  904.  
  905.   if (url && docUrl && docScheme)
  906.   {
  907.     if (urlScheme)
  908.     {
  909.       // Url is absolute
  910.       // If we can make a relative URL, then enable must be true!
  911.       // (this lets the smarts of MakeRelativeUrl do all the hard work)
  912.       enable = (GetScheme(MakeRelativeUrl(url)).length == 0);
  913.     }
  914.     else
  915.     {
  916.       // Url is relative
  917.       // Check if url is a named anchor
  918.       //  but document doesn't have a filename
  919.       // (it's probably "index.html" or "index.htm",
  920.       //  but we don't want to allow a malformed URL)
  921.       if (url[0] == "#")
  922.       {
  923.         var docFilename = GetFilename(docUrl);
  924.         enable = docFilename.length > 0;
  925.       }
  926.       else
  927.       {
  928.         // Any other url is assumed 
  929.         //  to be ok to try to make absolute
  930.         enable = true;
  931.       }
  932.     }
  933.   }
  934.  
  935.   SetElementEnabled(checkbox, enable);
  936. }
  937.  
  938. // oncommand handler for the Relativize checkbox in EditorOverlay.xul
  939. function MakeInputValueRelativeOrAbsolute(checkbox)
  940. {
  941.   var input =  document.getElementById(checkbox.getAttribute("for"));
  942.   if (!input)
  943.     return;
  944.  
  945.   var docUrl = GetDocumentBaseUrl();
  946.   if (!docUrl)
  947.   {
  948.     // Checkbox should be disabled if not saved,
  949.     //  but keep this error message in case we change that
  950.     AlertWithTitle("", GetString("SaveToUseRelativeUrl"));
  951.     window.focus();
  952.   }
  953.   else 
  954.   {
  955.     // Note that "checked" is opposite of its last state,
  956.     //  which determines what we want to do here
  957.     if (checkbox.checked)
  958.       input.value = MakeRelativeUrl(input.value);
  959.     else
  960.       input.value = MakeAbsoluteUrl(input.value);
  961.  
  962.     // Reset checkbox to reflect url state
  963.     SetRelativeCheckbox(checkbox);
  964.   }
  965. }
  966.  
  967. var IsBlockParent = {
  968.   APPLET: true,
  969.   BLOCKQUOTE: true,
  970.   BODY: true,
  971.   CENTER: true,
  972.   DD: true,
  973.   DIV: true,
  974.   FORM: true,
  975.   LI: true,
  976.   NOSCRIPT: true,
  977.   OBJECT: true,
  978.   TD: true,
  979.   TH: true
  980. };
  981.  
  982. var NotAnInlineParent = {
  983.   COL: true,
  984.   COLGROUP: true,
  985.   DL: true,
  986.   DIR: true,
  987.   MENU: true,
  988.   OL: true,
  989.   TABLE: true,
  990.   TBODY: true,
  991.   TFOOT: true,
  992.   THEAD: true,
  993.   TR: true,
  994.   UL: true
  995. };
  996.  
  997. function nodeDepth(node)
  998. {
  999.   for (var depth = 0; node != null; depth++)
  1000.     node = node.parentNode;
  1001.   return depth;
  1002. }
  1003.  
  1004. function nthParent(node, n)
  1005. {
  1006.   for (; n > 0; n--)
  1007.     node = node.parentNode;
  1008.   return node;
  1009. }
  1010.  
  1011. function nodeIsBlock(node)
  1012. {
  1013.   // HR doesn't count because it's not a container
  1014.   return !node || (node.localName != 'HR' && editorShell.NodeIsBlock(node));
  1015. }
  1016.  
  1017. /* Ugly code alert! If only I could do this:
  1018.  * var range = editorShell.editorSelection;
  1019.  * range = editorShell.FlattenRange(range); // ensure anchorNode == parentNode
  1020.  * if (editorShell.IsInlineRange(range) && editorShell.NodeIsBlock(node)) return false;
  1021.  * while (!editorShell.ContainmentAllowed(range.anchorNode, element))
  1022.  *   range = editorShell.ParentRangeOf(range);
  1023.  * editorShell.InsertElementAtRange(element, range);
  1024.  * return true;
  1025.  */
  1026. function InsertElementAroundSelection(element)
  1027. {
  1028.   // We need to find a suitable container for the element.
  1029.   // First get the selection
  1030.   var anchorParent = editorShell.editorSelection.anchorNode;
  1031.   if (!anchorParent.localName)
  1032.     var anchorSelected = true;
  1033.   else if (editorShell.editorSelection.anchorOffset < anchorParent.childNodes.length)
  1034.     var anchor = anchorParent.childNodes[editorShell.editorSelection.anchorOffset];
  1035.   var focusParent = editorShell.editorSelection.focusNode;
  1036.   if (!focusParent.localName)
  1037.     var focusSelected = true;
  1038.   else if (editorShell.editorSelection.focusOffset < focusParent.childNodes.length)
  1039.     var focus = focusParent.childNodes[editorShell.editorSelection.focusOffset];
  1040.  
  1041.   // Find the common ancestor
  1042.   var anchorDepth = nodeDepth(anchorParent);
  1043.   var focusDepth = nodeDepth(focusParent);
  1044.   if (anchorDepth > focusDepth)
  1045.   {
  1046.     anchor = nthParent(anchorParent, anchorDepth - focusDepth - 1);
  1047.     anchorParent = anchor.parentNode;
  1048.     anchorSelected = true;
  1049.   }
  1050.   else if (anchorDepth < focusDepth)
  1051.   {
  1052.     focus = nthParent(focusParent, focusDepth - anchorDepth - 1);
  1053.     focusParent = focus.parentNode;
  1054.     focusSelected = true;
  1055.   }
  1056.   var ordered = false;
  1057.   while (anchorParent != focusParent)
  1058.   {
  1059.     anchor = anchorParent;
  1060.     anchorParent = anchor.parentNode;
  1061.     focus = focusParent;
  1062.     focusParent = focus.parentNode;
  1063.     anchorSelected = focusSelected = true;
  1064.   }
  1065.  
  1066.   // The common ancestor may not be suitable, so find a suitable one.
  1067.   if (editorShell.NodeIsBlock(element))
  1068.   {
  1069.     // Block element parent must be a valid block
  1070.     while (!(anchorParent.localName in IsBlockParent))
  1071.     {
  1072.       anchor = focus = anchorParent;
  1073.       anchorSelected = focusSelected = true;
  1074.       anchorParent = anchor.parentNode;
  1075.       ordered = true;
  1076.     }
  1077.   }
  1078.   else
  1079.   {
  1080.     // Inline element parent must not be an invalid block
  1081.     while (anchorParent.localName in NotAnInlineParent)
  1082.     {
  1083.       anchor = focus = anchorParent;
  1084.       anchorSelected = focusSelected = true;
  1085.       anchorParent = anchor.parentNode;
  1086.       ordered = true;
  1087.     }
  1088.   }
  1089.  
  1090.   // We now have an ancestor to hold the element
  1091.   // and a range of child nodes to move into the element
  1092.   if (anchor != focus)
  1093.   {
  1094.     if (!ordered)
  1095.     {
  1096.       // Ensure anchor <= focus
  1097.       for (var node = anchorParent.firstChild; node != anchor; node = node.nextSibling)
  1098.       {
  1099.         if (node == focus)
  1100.         {
  1101.           focus = anchor;
  1102.           anchor = node;
  1103.           focusSelected = anchorSelected;
  1104.           break;
  1105.         }
  1106.       }
  1107.     }
  1108.     if (focus && !focusSelected)
  1109.       focus = focus.previousSibling;
  1110.   }
  1111.   if (!editorShell.NodeIsBlock(element))
  1112.   {
  1113.     // Fail if we're not inserting a block
  1114.     if (!anchor) return false;
  1115.     for (node = anchor; ; node = node.nextSibling)
  1116.       if (!node)
  1117.         return false;
  1118.       else if (nodeIsBlock(node))
  1119.         break;
  1120.       else if (node == focus)
  1121.         return false;
  1122.   }
  1123.  
  1124.   // The range may be contained by body text, which should all be selected.
  1125.   if (!nodeIsBlock(anchor))
  1126.     while (!nodeIsBlock(anchor.previousSibling))
  1127.       anchor = anchor.previousSibling;
  1128.   if (!nodeIsBlock(focus))
  1129.     while (!nodeIsBlock(focus.nextSibling))
  1130.       focus = focus.nextSibling;
  1131.  
  1132.   editorShell.BeginBatchChanges();
  1133.   try {
  1134.     var anchorOffset = 0;
  1135.     // Calculate the insertion point for the undoable InsertElement method
  1136.     if (!anchor)
  1137.       anchor = anchorParent.firstChild;
  1138.     else
  1139.       for (node = anchorParent.firstChild; node != anchor; node = node.nextSibling)
  1140.         anchorOffset++;
  1141.     editorShell.InsertElement(element, anchorParent, anchorOffset, true);
  1142.     // Move all the old child nodes to the element
  1143.     // Use editor methods in case of text nodes
  1144.     var editor = editorShell.editor;
  1145.     while (anchor)
  1146.     {
  1147.       node = anchor.nextSibling;
  1148.       editor.deleteNode(anchor);
  1149.       editor.insertNode(anchor, element, element.childNodes.length);
  1150.       if (anchor == focus) break;
  1151.       anchor = node;
  1152.     }
  1153.   }
  1154.   catch (ex) {}
  1155.  
  1156.   editorShell.EndBatchChanges();
  1157.   return true;
  1158. }
  1159.  
  1160. // Should I set the selection to the element, then insert HTML the element's innerHTML?
  1161. // I would prefer to say editorShell.DeleteElement(element, FLAG_TO_KEEP_CHILD_NODES);
  1162. function RemoveElementKeepingChildren(element)
  1163. {
  1164.   editorShell.BeginBatchChanges();
  1165.   try {
  1166.     if (element.firstChild)
  1167.     {
  1168.       // Use editor methods in case of text nodes
  1169.       var editor = editorShell.editor;
  1170.       var parent = element.parentNode;
  1171.       var offset = 0;
  1172.       for (var node = parent.firstChild; node != element; node = node.nextSibling)
  1173.         offset++;
  1174.       while ((node = element.firstChild))
  1175.       {
  1176.         editor.deleteNode(node);
  1177.         editor.insertNode(node, parent, offset++);
  1178.       }
  1179.     }
  1180.     editorShell.DeleteElement(element);
  1181.   }
  1182.   catch (ex) {}
  1183.  
  1184.   editorShell.EndBatchChanges();
  1185. }
  1186.  
  1187. function FillLinkMenulist(linkMenulist, headingsArray)
  1188. {
  1189.   var NamedAnchorNodeList = editorShell.editorDocument.anchors;
  1190.   var NamedAnchorCount = NamedAnchorNodeList.length;
  1191.   if (NamedAnchorCount > 0)
  1192.   {
  1193.     for (var i = 0; i < NamedAnchorCount; i++)
  1194.       linkMenulist.appendItem("#" + NamedAnchorNodeList.item(i).name);
  1195.   } 
  1196.   for (var j = 1; j <= 6; j++)
  1197.   {
  1198.     var headingList = editorShell.editorDocument.getElementsByTagName("h" + j);
  1199.     for (var k = 0; k < headingList.length; k++)
  1200.     {
  1201.       var heading = headingList.item(k);
  1202.  
  1203.       // Skip headings that already have a named anchor as their first child
  1204.       //  (this may miss nearby anchors, but at least we don't insert another
  1205.       //   under the same heading)
  1206.       var child = heading.firstChild;
  1207.       if (child && child.nodeName == "A" && child.name && (child.name.length>0))
  1208.         continue;
  1209.  
  1210.       var range = editorShell.editorDocument.createRange();
  1211.       range.setStart(heading,0);
  1212.       var lastChildIndex = heading.childNodes.length;
  1213.       range.setEnd(heading,lastChildIndex);
  1214.       var text = range.toString();
  1215.       if (text)
  1216.       {
  1217.         // Use just first 40 characters, don't add "...",
  1218.         //  and replace whitespace with "_" and strip non-word characters
  1219.         text = "#" + ConvertToCDATAString(TruncateStringAtWordEnd(text, 40, false));
  1220.         // Append "_" to any name already in the list
  1221.         while (linkMenulist.getElementsByAttribute("label", text).length)
  1222.           text += "_";
  1223.         linkMenulist.appendItem(text);
  1224.  
  1225.         // Save nodes in an array so we can create anchor node under it later
  1226.         headingsArray[NamedAnchorCount++] = heading;
  1227.       }
  1228.     }
  1229.   }
  1230.   if (!linkMenulist.firstChild.hasChildNodes()) 
  1231.   {
  1232.     var item = linkMenulist.appendItem(GetString("NoNamedAnchorsOrHeadings"));
  1233.     item.setAttribute("disabled", "true");
  1234.   }
  1235. }
  1236.  
  1237. // Shared by Image and Link dialogs for the "Choose" button for links
  1238. function chooseLinkFile()
  1239. {
  1240.   // Get a local file, converted into URL format
  1241.   var fileName = GetLocalFileURL("html, img");
  1242.   if (fileName) 
  1243.   {
  1244.     // Always try to relativize local file URLs
  1245.     if (gHaveDocumentUrl)
  1246.       fileName = MakeRelativeUrl(fileName);
  1247.  
  1248.     gDialog.hrefInput.value = fileName;
  1249.  
  1250.     // Do stuff specific to a particular dialog
  1251.     // (This is defined separately in Image and Link dialogs)
  1252.     ChangeLinkLocation();
  1253.   }
  1254.   // Put focus into the input field
  1255.   SetTextboxFocus(gDialog.hrefInput);
  1256. }
  1257.  
  1258.